
package com.phm.phmneuron;

import com.phm.phmneuron.connection.Connection;
import com.phm.phmneuron.neuron.Neuron;
import com.phm.phmneuron.neuron.NeuronsContainer;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *
 * @author PARHAM
 */
public abstract class NeuralNetwork {
    
    protected final static int DEFAULT_WORKER_QUANTITY = 10;
    
    public final static int NUM_NEURONS = 0x0001;
    public final static int NUM_FEED_SIGNALS = 0x0002;
    public final static int NUM_CONNECTIONS = 0x0003;
    
    public final ParametersContainer parameters = new ParametersContainer();
    protected final NeuronsContainer neurons = new NeuronsContainer();
    
    protected LinkedList<EventListener> _eventListeners = new LinkedList<>();
    protected LinkedList<NeuralNetworkRestriction> _restrictHandlers = new LinkedList<>();
    protected ExecutorService _eventWorkerPool = Executors.newFixedThreadPool(DEFAULT_WORKER_QUANTITY);
   
    public NeuralNetwork () {
        parameters.updateParameter(NUM_FEED_SIGNALS, 0);
        parameters.updateParameter(NUM_CONNECTIONS, 0);
        parameters.updateParameter(NUM_NEURONS, 0);
    }
    
    public void addRestrictionHandler (NeuralNetworkRestriction r) {
        _restrictHandlers.add(Objects.requireNonNull(r));
    }
    public void removeRestrictionHandler (NeuralNetworkRestriction r) {
        _restrictHandlers.remove(Objects.requireNonNull(r));
    }
    public List<NeuralNetworkRestriction> getRestrictionHandlers () {
        return _restrictHandlers;
    }
    public void clearRestrictionHandlers () {
        _restrictHandlers.clear();
    }
    
    public void addEventListener (EventListener listener) {
        _eventListeners.add(Objects.requireNonNull(listener));
    }
    public void removeEventListener (EventListener listener) {
        _eventListeners.remove(Objects.requireNonNull(listener));
    }
    public List<EventListener> getEventListeners () {
        return _eventListeners;
    }
    public void clearEventListeners () {
        _eventListeners.clear();
    }
    
    protected void eventNetworkUpdated () {
        final NeuralNetwork obj = this;
        _eventWorkerPool.execute(new Runnable() {
            @Override
            public void run() {
                for (EventListener x : _eventListeners) {
                    x.networkUpdated(obj);
                }
            }
        });
    }
    protected void eventNetworkStarted () {
        final NeuralNetwork obj = this;
        _eventWorkerPool.execute(new Runnable() {
            @Override
            public void run() {
                for (EventListener x : _eventListeners) {
                    x.networkStarted(obj);
                }
            }
        });
    }
    protected void eventNetworkStoped () {
        final NeuralNetwork obj = this;
        _eventWorkerPool.execute(new Runnable() {
            @Override
            public void run() {
                for (EventListener x : _eventListeners) {
                    x.networkStoped(obj);
                }
            }
        });
    }
    protected void eventNeuronAdded (Neuron n) {
        final Neuron obj = n;
        _eventWorkerPool.execute(new Runnable() {
            @Override
            public void run() {
                for (EventListener x : _eventListeners) {
                    x.neuronAdded(obj);
                }
            }
        });
    }
    protected void eventNeuronRemoved (Neuron n) {
        final Neuron obj = n;
        _eventWorkerPool.execute(new Runnable() {
            @Override
            public void run() {
                for (EventListener x : _eventListeners) {
                    x.neuronRemoved(obj);
                }
            }
        });
    }
    protected void eventConnectionAdded (Connection c) {
        final Connection obj = c;
        _eventWorkerPool.execute(new Runnable() {
            @Override
            public void run() {
                for (EventListener x : _eventListeners) {
                    x.connectionAdded(obj);
                }
            }
        });
    }
    protected void eventConnectionRemoved (Connection c) {
        final Connection obj = c;
        _eventWorkerPool.execute(new Runnable() {
            @Override
            public void run() {
                for (EventListener x : _eventListeners) {
                    x.connectionRemoved(obj);
                }
            }
        });
    }

    protected boolean isRestrictionsFulfiled () {
        for (NeuralNetworkRestriction x : _restrictHandlers) {
            if (x.fulfil(this)) {
                return true;
            }
        }
        return false;
    }
    
    public boolean addNeuron (Neuron neuron) {
        if (neuron != null &&
            neurons.addNeuron(neuron)) {
            parameters.processOnParameter(NUM_NEURONS, new IncreaseQuantity());
            this.eventNeuronAdded(neuron);
            return true;
        }
        return false;
    }
    public boolean removeNeuron (Neuron neuron) {
        if (neuron != null && 
            neurons.removeNeuron(neuron)) {
            parameters.processOnParameter(NUM_NEURONS, new DecreaseQuantity());
            this.eventNeuronRemoved(neuron);
            return true;
        }
        return false;
    }
    public void clearNeurons () {
        parameters.updateParameter(NUM_NEURONS, 0);
        neurons.clearNeurons();
    }
    public List<Neuron> getNeurons () {
        return Arrays.asList(neurons.getNeurons());
    }
    
    public boolean updateConnection (Connection c) {
        if (c == null) return false;
        if (c.neuronOne.connections.add(c) &&
            c.neuronTwo.connections.add(c)) {
            parameters.processOnParameter(NUM_CONNECTIONS, new IncreaseQuantity());
            eventConnectionAdded(c);
        } else {
            // Remove the previous connection
            c.neuronOne.connections.remove(c);
            c.neuronTwo.connections.remove(c);
            eventConnectionRemoved(c);
            // Add the updated connection
            c.neuronOne.connections.add(c);
            c.neuronTwo.connections.add(c);
            eventConnectionAdded(c);
        }
        return true;
    }
    protected boolean removeConnection (Connection c) {
        if (c == null) return false;
        if (c.neuronOne.connections.remove(c) &&
            c.neuronTwo.connections.remove(c)) {
            parameters.processOnParameter(NUM_CONNECTIONS, new DecreaseQuantity());
            eventConnectionRemoved(c);
            return true;
        }
        return false;
    }
    
    
    public boolean learn (List<float []> signals) {
        eventNetworkStarted();
        if (learnMethod(signals)) {
            eventNetworkStoped();
            return true;
        }
        return false;
    }
    protected abstract boolean learnMethod (List<float []> signals);
    public abstract boolean learn (float [] signal);
    public abstract Neuron result (float [] signal);
    
    public class IncreaseQuantity extends ProcessOnParameter {
        @Override
        public Object process(Object data, ParametersContainer c) {
            int temp = (Integer) data;
            return temp + 1;
        }
    }
    public class DecreaseQuantity extends ProcessOnParameter {
        @Override
        public Object process(Object data, ParametersContainer c) {
            int temp = (Integer) data;
            return temp - 1;
        }
    }
}
